package cn.hd01.service.impl;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import javax.transaction.Transactional;

import com.google.common.collect.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import cn.hd01.repository.PermissionsResp;
import cn.hd01.repository.RolePermissionsResp;
import cn.hd01.repository.RoleRepository;
import cn.hd01.repository.entity.Permissions;
import cn.hd01.repository.entity.RolePermissions;
import cn.hd01.service.PermissionsService;
import cn.hd01.util.TreeNode;

@Service
public class PermissionsServiceImpl extends BaseServiceImpl<Permissions, Integer> implements PermissionsService {

	private final TreeNode<Permissions> EMPTY_PERMISSION_TREE;

	private PermissionsResp repository;

	@Autowired
	private RolePermissionsResp rolePermissionsResp;

	@Autowired
	private RoleRepository roleRepository;

	@Autowired
	public PermissionsServiceImpl(PermissionsResp repository) {
		super(repository);
		this.repository = repository;
		EMPTY_PERMISSION_TREE = build(new TreeNode<Permissions>(), Lists.<Permissions>newArrayListWithCapacity(0), -1);
	}

	/* 
	 * @Cacheable
	 */
	@Override
	@Cacheable(value = "permissions", key = "'PermissionsTree'")
	public TreeNode<Permissions> getPermissionsTree() {
		List<Permissions> list = repository.findAll(new Sort(Direction.ASC, "menuOrder"));
		return build(new TreeNode<Permissions>(), list, -1);
	}

	@Override
	@Cacheable(value = "menu", key = "#p0", unless = "#result == null")
	public TreeNode<Permissions> findMenuByRoleId(final int roleid) {
		List<Permissions> list = repository.findAll(new Specification<Permissions>() {

			@Override
			public Predicate toPredicate(Root<Permissions> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
				Subquery<Integer> sq = query.subquery(Integer.class);
				Root<RolePermissions> rp = sq.from(RolePermissions.class);
				sq.where(cb.equal(rp.get("roleId"), roleid)).select(rp.get("permissionId").as(Integer.class));

				return cb.and(root.get("id").in(sq), cb.equal(root.get("isMenu"), true));
			}
		}, new Sort(Direction.ASC, "menuOrder"));

		if (CollectionUtils.isEmpty(list)) {
			return EMPTY_PERMISSION_TREE;
		}

		return build(new TreeNode<Permissions>(), list, -1);
	}

	private static TreeNode<Permissions> build(TreeNode<Permissions> head, List<Permissions> list, int id) {
		for (Permissions p : list) {
			if (p.getParentid().equals(id)) {
				head.addChildren(build(new TreeNode<Permissions>(p), list, p.getId()));
			}
		}

		return head;
	}

	@Override
	@Transactional
	@CacheEvict(value = {"menu", "permission"}, key = "#p0")
	public void save(int id, List<Integer> pIds) {
		if (!roleRepository.exists(id)) {
			return;
		}

		List<RolePermissions> list = new ArrayList<RolePermissions>();
		for (Integer pid : pIds) {
			list.add(new RolePermissions(id, pid));
		}

		rolePermissionsResp.deleteByRoleId(id);
		rolePermissionsResp.save(list);
	}

	@Override
	@Cacheable(value = "permission", key = "#p0", unless = "#result == null")
	public List<Permissions> findPermissionsCached(final int roleid) {
		List<Permissions> list = repository.findAll(new Specification<Permissions>() {

			@Override
			public Predicate toPredicate(Root<Permissions> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
				Subquery<Integer> sq = query.subquery(Integer.class);
				Root<RolePermissions> rp = sq.from(RolePermissions.class);
				sq.where(cb.equal(rp.get("roleId"), roleid)).select(rp.get("permissionId").as(Integer.class));

				return cb.and(root.get("id").in(sq));
			}
		});

		return list;
	}

	@Override
	public List<RolePermissions> findPermissions(int roleid) {
		return rolePermissionsResp.findByRoleId(roleid);
	}
}
